package net.aiion.weave.impl.bytecode.asm;

import net.aiion.weave.asm.AnnotationVisitor;
import net.aiion.weave.asm.Attribute;
import net.aiion.weave.asm.Handle;
import net.aiion.weave.asm.Label;
import net.aiion.weave.asm.MethodVisitor;
import net.aiion.weave.asm.Opcodes;
import net.aiion.weave.spi.bytecode.BytecodeEventFactory;

public class BytecodeASMMethodVisitor extends MethodVisitor implements Opcodes
{
    private final BytecodeEventFactory mBytecodeEventFactory;
    private final BytecodeASMAction mBytecodeAction;

    private BytecodeASMAnnotationVisitor annotationVisitor;

    private boolean isCancelled;

    public BytecodeASMMethodVisitor(final BytecodeEventFactory aBytecodeEventFactory,
        final BytecodeASMAction aBytecodeAction)
    {
        super(ASM4);
        mBytecodeEventFactory = aBytecodeEventFactory;
        mBytecodeAction = aBytecodeAction;
    }

    public void cancelVisitation() {
        isCancelled = true;

        if (annotationVisitor != null) {
            annotationVisitor.cancelVisitation();
        }
    }

    /*
     * The methods of this class must be called in the following order: 
     * [ visitAnnotationDefault ] 
     * ( visitAnnotation | visitParameterAnnotation | visitAttribute )* 
     * [ visitCode 
     * ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber 
     * )* visitMaxs ] 
     * visitEnd.
     * 
     * In addition, the visitXInsn and visitLabel methods must be called in the sequential order of the bytecode 
     * instructions of the visited code, visitTryCatchBlock must be called before the labels passed as arguments 
     * have been visited, and the visitLocalVariable and visitLineNumber methods must be called after the labels 
     * passed as arguments have been visited.
     */

    @Override
    public AnnotationVisitor visitAnnotationDefault() {
        if (isCancelled) {
            return null;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createAnnotationDefaultStart());
        annotationVisitor = new BytecodeASMAnnotationVisitor(mBytecodeEventFactory, mBytecodeAction);
        return annotationVisitor;
    }

    @Override
    public AnnotationVisitor visitAnnotation(final String aDescriptor, final boolean aVisible) {
        if (isCancelled) {
            return null;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createAnnotationStart(aDescriptor, aVisible));
        annotationVisitor = new BytecodeASMAnnotationVisitor(mBytecodeEventFactory, mBytecodeAction);
        return annotationVisitor;
    }

    @Override
    public AnnotationVisitor visitParameterAnnotation(final int aParameterIndex, final String aDescriptor,
        final boolean aVisible)
    {
        if (isCancelled) {
            return null;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createParameterAnnotationStart(aParameterIndex,
            aDescriptor, aVisible));
        annotationVisitor = new BytecodeASMAnnotationVisitor(mBytecodeEventFactory, mBytecodeAction);
        return annotationVisitor;
    }

    @Override
    public void visitAttribute(final Attribute aAttribute) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createAttribute(aAttribute));
    }

    @Override
    public void visitCode() {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createCodeStart());
    }

    @Override
    public void visitFrame(final int aFrameType, final int aLocalVariables, final Object[] aLocalVariableTypes,
        final int aStackElements, final Object[] aStackElementTypes)
    {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createFrame(aFrameType, aLocalVariables,
            aLocalVariableTypes, aStackElements, aStackElementTypes));
    }

    @Override
    public void visitFieldInsn(final int aOpcode, final String aOwnerName, final String aFieldName,
        final String aFieldDescriptor)
    {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createFieldInstruction(aOpcode, aOwnerName, aFieldName,
            aFieldDescriptor));
    }

    @Override
    public void visitIincInsn(final int aLocalVariableIndex, final int aIncrement) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createIincInstruction(aLocalVariableIndex, aIncrement));
    }

    @Override
    public void visitInsn(final int aOpcode) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createInstruction(aOpcode));
    }

    @Override
    public void visitIntInsn(final int aOpcode, final int aOperand) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createIntInstruction(aOpcode, aOperand));
    }

    @Override
    public void visitInvokeDynamicInsn(final String aMethodName, final String aMethodDescriptor,
        final Handle aBootstrapMethodHandle, final Object... aBootstrapMethodArguments)
    {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createInvokeDynamicInstruction(aMethodName,
            aMethodDescriptor, aBootstrapMethodHandle, aBootstrapMethodArguments));
    }

    @Override
    public void visitJumpInsn(final int aOpcode, final Label aLabel) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createJumpInstruction(aOpcode, aLabel));
    }

    @Override
    public void visitLdcInsn(final Object aConstant) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createLoadConstantInstruction(aConstant));
    }

    @Override
    public void visitLookupSwitchInsn(final Label aDefaultLabel, final int[] aKeys, final Label[] aBlockLabels) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createLookupSwitchInstruction(aDefaultLabel, aKeys,
            aBlockLabels));
    }

    @Override
    public void visitMethodInsn(final int aOpcode, final String aOwnerName, final String aMethodName,
        final String aMethodDescriptor)
    {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createMethodInstruction(aOpcode, aOwnerName, aMethodName,
            aMethodDescriptor));
    }

    @Override
    public void visitMultiANewArrayInsn(final String aComponentDescriptor, final int aDimensions) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createMultiANewArrayInstruction(aComponentDescriptor,
            aDimensions));
    }

    @Override
    public void visitTableSwitchInsn(final int aMinimumKeyValue, final int aMaximumKeyValue, final Label aDefaultLabel,
        final Label... aBlockLabels)
    {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createTableSwitchInstruction(aMinimumKeyValue,
            aMaximumKeyValue, aDefaultLabel, aBlockLabels));
    }

    @Override
    public void visitTypeInsn(final int aOpcode, final String aTypeName) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createTypeInstruction(aOpcode, aTypeName));
    }

    @Override
    public void visitVarInsn(final int aOpcode, final int aLocalVariableIndex) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createVariableInstruction(aOpcode, aLocalVariableIndex));
    }

    @Override
    public void visitLabel(final Label aLabel) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createLabel(aLabel));
    }

    @Override
    public void visitTryCatchBlock(final Label aStartLabel, final Label aEndLabel, final Label aHandlerLabel,
        final String aExceptionName)
    {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createTryCatchBlock(aStartLabel, aEndLabel,
            aHandlerLabel, aExceptionName));
    }

    @Override
    public void visitLocalVariable(final String aName, final String aDescriptor, final String aSignature,
        final Label aStartScopeLabel, final Label aEndScopeLabel, final int aLocalVariableIndex)
    {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createLocalVariable(aName, aDescriptor, aSignature,
            aStartScopeLabel, aEndScopeLabel, aLocalVariableIndex));
    }

    @Override
    public void visitLineNumber(final int aLineNumber, final Label aStartLabel) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createLineNumber(aLineNumber, aStartLabel));
    }

    @Override
    public void visitMaxs(final int aMaximumStackSize, final int aMaximumLocalVariables) {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createCodeEnd(aMaximumStackSize, aMaximumLocalVariables));
    }

    @Override
    public void visitEnd() {
        if (isCancelled) {
            return;
        }

        mBytecodeAction.processBytecode(mBytecodeEventFactory.createMethodEnd());
    }
}
